#include "DecodeDSDIFF.h"
#include <fcntl.h>
#include <string.h>
#include <arpa/inet.h>

#ifdef __BYTE_ORDER
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#endif
#endif

#ifdef __LITTLE_ENDIAN__
#define ntohll(x) (((uint64_t)(ntohl((int)((x) & 0xFFFFFFFF))) << 32) | \
                     (uint64_t)ntohl(((uint32_t)((x) >> 32))))
#else
#define ntohll(x) (x)
#endif
DecodeDSDIFF::~DecodeDSDIFF(void)
{
    if (file_ > 0 && file_ != fileno(stdin))
	::close(file_);
}

#define READ(A) ASSERT(::read(file_, &A, sizeof(A)) == sizeof(A), "Error reading from file")
#define IS_ID(A,STR) (!memcmp(A, STR, 4))
#define TEST_ID(A,STR) ASSERT(IS_ID(A, STR), "Waiting for header of type " << STR)
bool DecodeDSDIFF::open(const std::string& file_name, double seek_seconds)
{
    if (file_name == "-")
	file_ = fileno(stdin);
    else
    {
	ASSERT((file_ = ::open(file_name.c_str(), O_RDONLY)) > 0,
	    "Error opening " << file_name);
    }
    
    ChunkHeader header;
    uint8_t id[4];

    READ(header);
    TEST_ID(header.id, "FRM8");
    READ(id);
    TEST_ID(id, "DSD ");

//    m_id3_offset = sizeof(ck) + ck.get_size();
    uint64_t frm8_end_ptr = ntohll(header.size) + 12; // File location for end of frm8.

    while (filePos() < frm8_end_ptr)
    {
	READ(header);
	if (IS_ID(header.id, "FVER") && ntohll(header.size) == 4)
	{
	    uint32_t version;
	    READ(version);
	    ASSERT(ntohl(version) == 0x01050000
		|| ntohl(version) == 0x01040000,
		"Unknown version " << ntohl(version));
	}
	else if (IS_ID(header.id, "PROP"))
	{
	    uint64_t prop_end_ptr = filePos() + ntohll(header.size);
	    READ(id);
	    TEST_ID(id, "SND ");
	    while (filePos() < prop_end_ptr)
	    {
		READ(header);
		if (IS_ID(header.id, "FS  "))
		{
		    ASSERT(ntohll(header.size) == 4, "");
		    READ(sample_rate_);
		    sample_rate_ = ntohl(sample_rate_);
		}
		else if (IS_ID(header.id, "CHNL"))
		{
		    READ(channel_count_);
		    channel_count_ = ntohs(channel_count_);
		    ASSERT(channel_count_ == 2, "Bad number of channels " << channel_count_);
		    ASSERT(ntohll(header.size) == 2u + channel_count_ * 4, "Bad chunk length");
		    // Assume this channel order.
		    READ(id);
		    TEST_ID(id, "SLFT");
		    READ(id);
		    TEST_ID(id, "SRGT");
		}
		else if (IS_ID(header.id, "CMPR"))
		{
		    READ(id);
		    ASSERT(!IS_ID(id, "DST "), "DST compression not supported");
		    TEST_ID(id, "DSD ");
		    // Skip compression name
		    fileJumpForward(ntohll(header.size) - 4);
		}
		else if (IS_ID(header.id, "LSCO"))
		{
		    ASSERT(ntohll(header.size) == 2, "Bad size");
		    uint16_t loudspeaker_config;
		    READ(loudspeaker_config);
		    ASSERT(ntohs(loudspeaker_config) == 0, "Bad loudspeaker config");
		}
		else if (IS_ID(header.id, "ID3 "))
		{
		    WARN("ID3 tag in PROP chunk");
		    fileJumpForward(ntohll(header.size));
		    //id3_offset = m_file->get_position();
		    //id3_size = ck.get_size();
		}
		else {
		    WARN("Unknown chunk in PROP: \"" << (char)header.id[0] << (char)header.id[1]
			<< (char)header.id[2] << (char)header.id[3]
			<< "\". Ignoring");
		    fileJumpForward(ntohll(header.size));
		}
		// Chunks end on 2 bytes boudary no matter what their size is.
		fileJumpForward(filePos() & 1);
	    }
	}
	else if (IS_ID(header.id, "DSD "))
	{
	    data_left_ = ntohll(header.size);
	    ASSERT(data_left_ % channel_count_ == 0, "Bad data size");
	    if (seek_seconds)
	    {
		uint64_t offs = seek_seconds * sample_rate_ / 8 * channel_count_;
		if (offs >= data_left_)
		    data_left_ = 0;
		else
		{
		    fileJumpForward(offs);
		    data_left_ -= offs;
		}
	    }
	    
	    return true;
	}
	else if (IS_ID(header.id, "ID3 "))
	{
	    WARN("ID3 tag in outer chunk");
	    fileJumpForward(ntohll(header.size));
	}
	else {
	    WARN("Unknown chunk: \"" << (char)header.id[0] << (char)header.id[1]
		<< (char)header.id[2] << (char)header.id[3] << "\"");
	    fileJumpForward(ntohll(header.size));
	}
	// Chunks end on 2 bytes boudary no matter what their size is.
	fileJumpForward(filePos() & 1);
    }

    return false; // No DSD data!
}

uint64_t DecodeDSDIFF::read(uint8_t* out, uint64_t size)
{
    ASSERT(size % channel_count_ == 0, "Bad requested bytes");
    if (data_left_ == 0)
	return 0;
    if (size > data_left_)
	size = data_left_;
    ASSERT(::read(file_, out, size) == (int64_t)size, "Can't read " << size << "bytes of data from file");
    data_left_ -= size;
    return size;
}

